package com.siashan.toolkit.lock4j;

import com.siashan.toolkit.lock4j.annotation.Lock;
import com.siashan.toolkit.lock4j.annotation.NoRepeat;
import com.siashan.toolkit.lock4j.exception.LockException;
import com.siashan.toolkit.lock4j.executor.LockExecutor;
import com.siashan.toolkit.lock4j.strategy.DefaultLockFailStrategy;
import com.siashan.toolkit.lock4j.strategy.LockFailStrategy;
import com.siashan.toolkit.lock4j.strategy.LockStrategy;
import com.siashan.toolkit.lock4j.util.LockUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;


/**
 * 分布式锁模板方法
 *
 * @author siashan
 * @since v1.0.1
 */
@SuppressWarnings("rawtypes")
@Slf4j
@Setter
@Getter
public class LockTemplate {

    /**
     * 锁配置
     */
    private Lock4jProperties properties;


    /**
     * 锁执行器
     */
    private LockExecutor lockExecutor;


    public LockTemplate() {
    }

    /**
     * 加锁
     *
     * @param key      锁key
     * @param lockable 锁枚举
     * @return 结果
     */
    public LockInfo lock(String key, Lock lockable) {
        LockFailStrategy lockFailStrategy = null;
        try {
            lockFailStrategy = lockable.failStrategy().newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return lock(key, lockable.expire(), lockable.acquireTimeout(), lockable.lockFailMsg(), lockable.lockStrategy(), lockFailStrategy);
    }


    /**
     * 加锁
     *
     * @param key      锁key
     * @param lockable 锁枚举
     * @return 结果
     */
    public LockInfo lock(String key, NoRepeat lockable) {
        LockFailStrategy lockFailStrategy = null;
        try {
            lockFailStrategy = lockable.failStrategy().newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return lock(key, lockable.expire(), lockable.acquireTimeout(), lockable.lockFailMsg(), lockable.lockStrategy(), lockFailStrategy);
    }


    /**
     * 加锁方法
     *
     * @param key            锁key 同一个key只能被一个客户端持有
     * @param expire         过期时间(ms) 防止死锁
     * @param acquireTimeout 尝试获取锁超时时间(ms)
     * @return 加锁成功返回锁信息 失败返回null
     */
    public LockInfo lock(String key, long expire, long acquireTimeout, String lockFailMsg) {
        return lock(key, expire, acquireTimeout, lockFailMsg, new DefaultLockFailStrategy());
    }

    /**
     * 加锁方法
     *
     * @param key            锁key 同一个key只能被一个客户端持有
     * @param expire         过期时间(ms) 防止死锁
     * @param acquireTimeout 尝试获取锁超时时间(ms)
     * @return 加锁成功返回锁信息 失败返回null
     */
    public LockInfo lock(String key, long expire, long acquireTimeout, String lockFailMsg, LockFailStrategy strategy) {
        return lock(key, expire, acquireTimeout, lockFailMsg, LockStrategy.KEEP_ACQUIRE, strategy);
    }


    /**
     * 加锁方法
     *
     * @param key            锁key 同一个key只能被一个客户端持有
     * @param expire         过期时间(ms) 防止死锁
     * @param acquireTimeout 尝试获取锁超时时间(ms)
     * @param lockFailMsg    加锁失败提示
     * @param lockStrategy   加锁超时策略
     * @param failStrategy   加锁失败策略
     * @return 加锁成功返回锁信息 失败返回null
     */
    public LockInfo lock(String key, long expire, long acquireTimeout, String lockFailMsg, LockStrategy lockStrategy, LockFailStrategy failStrategy) {
        key = properties.getKeyPrefix() + key;
        expire = expire == 0 ? properties.getExpireTimeout() : expire;
        acquireTimeout = acquireTimeout == 0 ? properties.getAcquireTimeout() : acquireTimeout;
        long retryInterval = properties.getRetryInterval();
        // 防止重试时间大于超时时间
        if (retryInterval >= acquireTimeout) {
            log.warn("retryInterval more than acquireTimeout,please check your configuration");
        }
        log.debug(String.format("use lock class: %s", lockExecutor.getClass()));
        int acquireCount = 0;
        String value = LockUtil.simpleUUID();
        long start = System.currentTimeMillis();
        Object lockInstance = null;
        try {

            // 处理不同的策略
            switch (lockStrategy) {
                case FAIL_FAST:
                    lockInstance = lockExecutor.acquire(key, value, expire, acquireTimeout);
                    if (null != lockInstance) {
                        return new LockInfo(key, value, expire, acquireTimeout, acquireCount, lockInstance);
                    }
                    break;
                case KEEP_ACQUIRE:
                    while (System.currentTimeMillis() - start < acquireTimeout) {
                        acquireCount++;
                        lockInstance = lockExecutor.acquire(key, value, expire, acquireTimeout);
                        if (null != lockInstance) {
                            return new LockInfo(key, value, expire, acquireTimeout, acquireCount, lockInstance);
                        }
                        TimeUnit.MILLISECONDS.sleep(retryInterval);
                    }
                    break;
                default:
                    break;
            }
        } catch (InterruptedException e) {
            log.error("lock error", e);
            throw new LockException();
        }
        // 加锁失败逻辑处理
        failStrategy.onLockFailure(key, acquireTimeout, acquireCount, lockFailMsg);
        return null;
    }


    /**
     * 释放锁
     *
     * @param lockInfo 锁信息
     * @return 释放结果
     */
    @SuppressWarnings("unchecked")
    public boolean releaseLock(LockInfo lockInfo) {
        if (null == lockInfo) {
            return false;
        }
        return lockExecutor.releaseLock(lockInfo.getLockKey(), lockInfo.getLockValue(),
                lockInfo.getLockInstance());
    }

}
